/*
 * Copyright 2012 Finbudget.com
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
package finbudgettable.client.ui.grid.cell.list;

import com.google.gwt.cell.client.ValueUpdater;
import com.google.gwt.core.client.GWT;
import com.google.gwt.core.client.Scheduler;
import com.google.gwt.dom.client.*;
import com.google.gwt.event.dom.client.KeyCodes;
import com.google.gwt.i18n.client.Constants;
import com.google.gwt.resources.client.ClientBundle;
import com.google.gwt.resources.client.CssResource;
import com.google.gwt.safehtml.client.SafeHtmlTemplates;
import com.google.gwt.safehtml.shared.SafeHtml;
import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
import finbudget.client.lists.GetListOfItems;
import finbudgettable.client.ui.grid.cell.EditableGridCell;

import java.util.List;

import static finbudget.client.lists.GetListOfItems.BudgetListItem;

/**
 * A dropdown list cell. Click to edit, escape to cancel, return to commit.
 *
 */
public class ListCell extends EditableGridCell<String, String> {

    interface Template extends SafeHtmlTemplates {

        @Template("<select>{0}</select>")
        SafeHtml select(SafeHtml options);

        @Template("<option value=\"{0}\">{1}</option>")
        SafeHtml option(String key, String value);

        @Template("<option selected value=\"{0}\">{1}</option>")
        SafeHtml selectedOption(String key, String value);

        @Template("<div class=\"{0}\">{1}</div>")
        SafeHtml preview(String className, String value);

        @Template("<a tabindex=\"-1\"><div class=\"{0}\">{1}</div></a>")
        SafeHtml loading(String className, String value);
    }

    interface Resources extends ClientBundle {

        interface Style extends CssResource {
            @ClassName("b-list-cell__text")
            String bListCell__text();

            @ClassName("b-list-cell__text_loading")
            String bListCell__text_loading();

            @ClassName("b-list-cell__multiline_text_loading")
            String bListCell__multiline_text_loading();

            @ClassName("b-list-cell__multiline_text")
            String bListCell__multiline_text();

            @ClassName("b-list-cell")
            String bListCell();

            @ClassName("b-list-filter-panel")
            String bListFilterPanel();
        }

        @Source("ListCell.css")
        Style style();
    }

    interface Strings extends Constants {
        @DefaultStringValue("загрузка данных...")
        String loading();

        @DefaultStringValue("< пусто >")
        String nullvalue();
    }

    private static final Template TEMPLATE = GWT.create(Template.class);
    static final Resources RESOURCES = GWT.create(Resources.class);
    private static final Strings STRINGS = GWT.create(Strings.class);

    static {
        RESOURCES.style().ensureInjected();
    }

    private final List<GetListOfItems.BudgetListItem> list;
    private final boolean nullable;

    @Override
    protected void showIncorrectValueError(Context context, Element parent, ViewData viewData) {
        throw new IllegalStateException();
    }

    public ListCell(boolean nullable, List<GetListOfItems.BudgetListItem> list) {
        super(nullable);
        this.nullable = nullable;
        this.list = list;
    }

    @Override
    protected boolean validate(String value) {
        return true;
    }

    @Override
    protected SafeHtml getEditorHtml(ViewData viewData) {
        //GWT.log("render editor");
        SafeHtmlBuilder sb = new SafeHtmlBuilder();
        if (!isListLoaded()) {
            if (isMultilineStyle()) {
                return TEMPLATE.loading(RESOURCES.style().bListCell__multiline_text_loading(), STRINGS.loading());
            } else {
                return TEMPLATE.loading(RESOURCES.style().bListCell__text_loading(), STRINGS.loading());
            }
        }
        String currentKey = viewData.getCurrentValue();
        if (nullable) {
            if (currentKey != null) {
                sb.append(TEMPLATE.selectedOption("", STRINGS.nullvalue()));
            } else {
                sb.append(TEMPLATE.option("", STRINGS.nullvalue()));
            }
        }
        for (BudgetListItem item : list) {
            if (item.key.equals(currentKey)) {
                sb.append(TEMPLATE.selectedOption(item.key, item.name));
            } else {
                sb.append(TEMPLATE.option(item.key, item.name));
            }
        }
        return TEMPLATE.select(sb.toSafeHtml());
    }

    private String getListValue(String key) {
        for (BudgetListItem item : list) {
            if (item.key.equalsIgnoreCase(key)) {
                return item.name;
            }
        }
        return key;
    }

    @Override
    protected SafeHtml getViewHtml(String key) {
        //GWT.log("render view");
        String className =
                isMultilineStyle() ?
                        RESOURCES.style().bListCell__multiline_text()
                        : RESOURCES.style().bListCell__text();

        if (key != null && !key.isEmpty()) {
            return TEMPLATE.preview(className, getListValue(key));
        } else {
            return TEMPLATE.preview(className, STRINGS.nullvalue());
        }
    }

    @Override
    protected SafeHtml getErrorHtml(String value) {
        throw new IllegalStateException();
    }

    protected SelectElement getSelectElement(Element parent) {
        return parent.getFirstChildElement().cast();
    }

    @Override
    protected void setFocus(Element parent, boolean focused) {
        SelectElement select = getSelectElement(parent);
        if (select == null) {
            parent.focus();
        } else {
            select.focus();
        }
    }

    @Override
    protected String getEditedValue(Element parent) {
        InputElement input = parent.getFirstChild().cast();
        return input.getValue();
    }

    private void repeatEvent(final Element parent, final int keyCode) {
        Scheduler.get().scheduleFinally(new Scheduler.ScheduledCommand() {
            @Override
            public void execute() {
                parent.dispatchEvent(Document.get().createKeyDownEvent(false, false, false, false, keyCode));
            }
        });
    }

    @Override
    protected void editEvent(Context context, Element parent, ViewData viewData, NativeEvent event,
                             ValueUpdater<String> valueUpdater) {

        String type = event.getType();

        if (BrowserEvents.BLUR.equals(type)) {
            EventTarget eventTarget = event.getEventTarget();
            if (Element.is(eventTarget)) {
                Element target = Element.as(eventTarget);
                String tag = target.getTagName().toLowerCase();
                if ("select".equals(tag)) {
                    commit(context, parent, viewData, valueUpdater);
                } else if ("a".equals(tag)) {
                    cancel(context, parent, viewData.getStoredValue(), viewData);
                }
            }
            return;
        }

        boolean keyUp = BrowserEvents.KEYUP.equals(type);
        boolean keyDown = BrowserEvents.KEYDOWN.equals(type);
        int keyCode = event.getKeyCode();

        if (!isListLoaded()) {
            if (keyDown
                    && (keyCode == KeyCodes.KEY_LEFT
                    || keyCode == KeyCodes.KEY_UP
                    || keyCode == KeyCodes.KEY_DOWN
                    || keyCode == KeyCodes.KEY_HOME
                    || keyCode == KeyCodes.KEY_RIGHT
                    || keyCode == KeyCodes.KEY_END)) {
                cancel(context, parent, viewData.getStoredValue(), viewData);
                repeatEvent(parent, keyCode);
                event.preventDefault();

            }
            return;
        }

        if (keyUp || keyDown) {
            if (keyDown && (keyCode == KeyCodes.KEY_LEFT || keyCode == KeyCodes.KEY_HOME)) {
                commit(context, parent, viewData, valueUpdater);
                repeatEvent(parent, keyCode);
                event.preventDefault();
                return;
            }
            if (keyDown && (keyCode == KeyCodes.KEY_RIGHT || keyCode == KeyCodes.KEY_END)) {
                commit(context, parent, viewData, valueUpdater);
                repeatEvent(parent, keyCode);
                event.preventDefault();
                return;
            }
        }

        super.editEvent(context, parent, viewData, event, valueUpdater);
    }

    @Override
    protected String convertValueToEditedValue(String value) {
        return value;
    }

    @Override
    protected String convertEditedValueToValue(String editedValue) {
        return editedValue;
    }

    @Override
    protected void edit(Context context, Element parent, String value) {
        super.edit(context, parent, value);
        SelectElement select = getSelectElement(parent);
        TableCellElement td = parent.getParentElement().cast();
        int height = td.getAbsoluteBottom() - td.getAbsoluteTop() - 4;
        select.getStyle().setHeight(height, Style.Unit.PX);
    }

    private boolean isListLoaded() {
        return !list.isEmpty();
    }
}